package com.zxy.ziems.collector.service;

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.json.JSONUtil;
import com.zxy.btp.common.util.JsonUtils;
import com.zxy.ziems.collector.config.CollectorProperties;
import com.zxy.ziems.collector.httpClient.CollectorRequest;
import com.zxy.ziems.collector.httpClient.Result;
import com.zxy.ziems.collector.job.CollectorModbusJob;
import com.zxy.ziems.collector.modbus.ModbusModel;
import com.zxy.ziems.collector.modbus.ModbusTcpTemplate;
import com.zxy.ziems.common.mqtt.BaseMqttConsumer;
import com.zxy.ziems.common.mqtt.annotation.MqttTopic;
import com.zxy.ziems.common.mqtt.pojo.Pattern;
import com.zxy.ziems.common.pojo.collector.Channel;
import com.zxy.ziems.common.pojo.collector.Config;
import com.zxy.ziems.common.pojo.collector.Unit;
import com.zxy.ziems.common.pojo.modbus.DeviceConfigModbus;
import com.zxy.ziems.common.pojo.modbus.DevicePropertyConfigModbus;
import lombok.extern.slf4j.Slf4j;
import org.quartz.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.context.event.ApplicationReadyEvent;
import org.springframework.context.ApplicationListener;
import org.springframework.stereotype.Service;

import java.time.LocalDateTime;
import java.util.*;
import java.util.stream.Collectors;

import static org.quartz.SimpleScheduleBuilder.simpleSchedule;

/**
 * 解析配置的
 * @author 10348359
 * @create 2024-05-01 11:12
 */
@Service
@Slf4j
@MqttTopic(
        topic = "${ziems.topic.update-config}/${ziems.collector.code}",
        patten = Pattern.NONE,
        qos = 1
)
public class ProcessConfigurationService extends BaseMqttConsumer<Config> implements ApplicationListener<ApplicationReadyEvent> {

    private LocalDateTime configTime = LocalDateTime.now();

    /**
     * 请求配置
     * @throws SchedulerException
     */
    public void requestConfig() throws SchedulerException {
        Result<Config> config = request.getConfig(collectorProperties.getCode());
        log.info(JSONUtil.toJsonStr(config));
        if (Objects.nonNull(config) && config.isSuccess()) {
            restartScheduler();
            doProcessJobByConfig(config.getData());
        }
    }
    public void restartScheduler() {
        try {
            if (scheduler.isInStandbyMode()) {
                return;
            }
            scheduler.standby();
            if (CollectionUtil.isNotEmpty(jobKeyList)) {
                scheduler.deleteJobs(jobKeyList);
                jobKeyList.clear();
            }
            modbusTcpTemplateHashMap.forEach((key, template)-> {
                template.disconnect();
            });
            scheduler.start();
        } catch (Exception e) {
            e.printStackTrace();
            log.error("[采集器][停止异常]{}", e.getLocalizedMessage());
        }
    }

    public void stopScheduler() {
        try {
            if (scheduler.isShutdown()) {
                return;
            }
            scheduler.pauseAll();
            modbusTcpTemplateHashMap.forEach((key, template)-> {
                template.disconnect();
            });
            scheduler.shutdown(true);
        } catch (Exception e) {
            e.printStackTrace();
            log.error("[采集器][停止异常]{}", e.getLocalizedMessage());
        }
    }

    HashMap<String, ModbusTcpTemplate> modbusTcpTemplateHashMap = new HashMap<>();

    private void doProcessJobByConfig(Config config) {
        configTime = LocalDateTime.now();

        Map<String, Unit> unitMap = config.getUnits().stream().collect(Collectors.toMap(Unit::getId, (a) -> a));

        config.getChannels().forEach((channel)-> {
            if ("ModbusTcp".equals(channel.getProtocol())) {
                String key = channel.getIp() + ":" +channel.getPort();

                ModbusTcpTemplate template;
                if (modbusTcpTemplateHashMap.containsKey(key)) {
                    template = modbusTcpTemplateHashMap.get(key);
                } else {
                    template = new ModbusTcpTemplate(channel.getIp(), channel.getPort());
                    modbusTcpTemplateHashMap.put(key, template);
                }

                doProcessModbusJobByChannel(channel, template, unitMap);
            }
        });
    }

    private List<JobKey> jobKeyList = new ArrayList<>();

    private void doProcessModbusJobByChannel(Channel channel, ModbusTcpTemplate template, Map<String, Unit> unitMap) {
        LinkedHashMap<Integer, ArrayList<ModbusModel>> modelsMap = new LinkedHashMap<>();
        String ip = channel.getIp();
        Integer port = channel.getPort();
        String deviceType = channel.getDeviceTypeCode();
        channel.getDevices().forEach(channelDevice -> {
            DeviceConfigModbus configModbus = JSONUtil.toBean(channelDevice.getAdvanced(), DeviceConfigModbus.class);
            String deviceId = channelDevice.getDeviceId();
            Integer station = configModbus.getStation();
            channelDevice.getPropertyList().forEach(deviceProperty -> {
                DevicePropertyConfigModbus propertyConfigModbus = JsonUtils.parseObject(deviceProperty.getAdvanced(), DevicePropertyConfigModbus.class);
                if (Objects.isNull(propertyConfigModbus)) {
                    log.error("[采集器]解析{}的高级配置时出错", deviceId);
                    return;
                }

                ModbusModel model = ModbusModel.builder()
                        .id(deviceProperty.getId())
                        .ip(ip)
                        .port(port)
                        .deviceId(deviceId)
                        .deviceType(deviceType)
                        .dataTypeId(deviceProperty.getDataTypeId())
                        .dataUnitId(deviceProperty.getDataUnitId())
                        .unitId(deviceProperty.getUnitId())
                        .station(station)
                        .functionCode(propertyConfigModbus.getFunctionCode())
                        .quantity(propertyConfigModbus.getQuantity())
                        .type(propertyConfigModbus.getType())
                        .startAddress(propertyConfigModbus.getStartAddress())
                        .byteOffset(propertyConfigModbus.getByteOffset())
                        .bitOffset(propertyConfigModbus.getBitOffset())
                        .littleEndian(propertyConfigModbus.isLittleEndian())
                        .collectInterval(deviceProperty.getCollectInterval())
                        .notifyType(deviceProperty.getNotifyType())
                        .build();
                Integer key = model.getCollectInterval();
                ArrayList<ModbusModel> models;
                if (modelsMap.containsKey(key)) {
                    models = modelsMap.get(key);
                } else {
                    models = new ArrayList<>();
                    modelsMap.put(key, models);
                }
                models.add(model);
            });
        });

        modelsMap.forEach((timeInterval, models) -> {
            JobDataMap dataMap = new JobDataMap();
            dataMap.put("models", models);
            dataMap.put("deviceType", deviceType);
            dataMap.put("template", template);
            dataMap.put("unitMap", unitMap);
            JobDetail job = JobBuilder.newJob(CollectorModbusJob.class)
                    .withIdentity(channel.getId() + "-job-" +timeInterval, channel.getId())
                    .usingJobData(dataMap)
                    .build();
            jobKeyList.add(job.getKey());

            // 构建Trigger实例
            Trigger trigger = TriggerBuilder.newTrigger()
                    .withIdentity(channel.getId() + "-trigger-" +timeInterval, channel.getId())
                    .forJob(job)
                    .startNow()
                    .withSchedule(
                            simpleSchedule()
                                    .withIntervalInMilliseconds(timeInterval)
                                    .repeatForever())
                    .build();

            try {
                scheduler.scheduleJob(job, trigger);
            } catch (SchedulerException e) {
                throw new RuntimeException(e);
            }
        });
    }

    @Override
    protected void doProcessMessage(String topic, Config config, LocalDateTime sendTime) {
        log.info("[采集器][配置更新了]");
        if (configTime.isAfter(sendTime)) {
            return;
        }
        restartScheduler();
        doProcessJobByConfig(config);
    }

    private CollectorProperties collectorProperties;

    @Autowired
    public void setCollectorProperties(CollectorProperties collectorProperties) {
        this.collectorProperties = collectorProperties;
    }

    private Scheduler scheduler;

    @Autowired
    public void setScheduler(Scheduler scheduler) {
        this.scheduler = scheduler;
    }

    CollectorRequest request;

    @Autowired
    public void setRequest(CollectorRequest collectorRequest) {
        this.request = collectorRequest;
    }

    @Override
    public void onApplicationEvent(ApplicationReadyEvent event) {

    }
}
